package cn.com.idmy.cloud.config;


import lombok.extern.slf4j.Slf4j;
import org.dromara.hutool.core.lang.Console;
import org.jetbrains.annotations.Nullable;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.cloud.client.ServiceInstance;
import org.springframework.cloud.client.discovery.DiscoveryClient;
import org.springframework.cloud.client.loadbalancer.Request;
import org.springframework.cloud.loadbalancer.core.DiscoveryClientServiceInstanceListSupplier;
import org.springframework.cloud.loadbalancer.core.RandomLoadBalancer;
import org.springframework.cloud.loadbalancer.core.ReactorLoadBalancer;
import org.springframework.cloud.loadbalancer.core.ServiceInstanceListSupplier;
import org.springframework.cloud.loadbalancer.support.LoadBalancerClientFactory;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Lazy;
import org.springframework.core.annotation.Order;
import org.springframework.core.env.Environment;
import reactor.core.publisher.Flux;

import java.net.NetworkInterface;
import java.util.List;
import java.util.stream.Collectors;

@Slf4j
@Configuration
@ConditionalOnProperty(name = "spring.cloud.loadbalancer.same-host.enabled", havingValue = "true")
class SameHostLoadBalancerConfiguration {
    public SameHostLoadBalancerConfiguration() {
        Console.error("已启用同 host 服务优先被调用【spring.cloud.loadbalancer.same-host.enabled】");
    }

    @Bean
    @Lazy
    ReactorLoadBalancer<ServiceInstance> randomLoadBalancer(Environment env, LoadBalancerClientFactory factory) {
        var name = env.getProperty(LoadBalancerClientFactory.PROPERTY_NAME);
        return new RandomLoadBalancer(factory.getLazyProvider(name, ServiceInstanceListSupplier.class), name);
    }

    @Bean
    @Lazy
    @Order(1)
    ServiceInstanceListSupplier discoveryClientServiceInstanceListSupplier(DiscoveryClient client, Environment env) {
        return new DiscoveryClientServiceInstanceListSupplier(client, env);
    }

    @Bean
    @Lazy
    @Order(2)
    ServiceInstanceListSupplier sameHostPreferenceServiceInstanceListSupplier(ServiceInstanceListSupplier delegate) {
        return new SameHostPreferenceServiceInstanceListSupplier(delegate);
    }

    static class SameHostPreferenceServiceInstanceListSupplier implements ServiceInstanceListSupplier {
        private final ServiceInstanceListSupplier delegate;
        private final String localIp;

        SameHostPreferenceServiceInstanceListSupplier(ServiceInstanceListSupplier delegate) {
            this.delegate = delegate;
            this.localIp = ip();
        }

        @Override
        public String getServiceId() {
            return delegate.getServiceId();
        }

        @Override
        public Flux<List<ServiceInstance>> get() {
            return delegate.get().map(this::selectSameHostInstances);
        }

        @Override
        public Flux<List<ServiceInstance>> get(Request request) {
            return delegate.get(request).map(this::selectSameHostInstances);
        }

        private List<ServiceInstance> selectSameHostInstances(List<ServiceInstance> instances) {
            if (localIp == null || instances.isEmpty()) {
                return instances;
            }
            var sameHostInstances = instances.stream()
                    .filter(instance -> localIp.equals(instance.getHost()))
                    .collect(Collectors.toList());
            return sameHostInstances.isEmpty() ? instances : sameHostInstances;
        }
    }

    @Nullable
    private static String ip() {
        try {
            var interfaces = NetworkInterface.getNetworkInterfaces();
            while (interfaces.hasMoreElements()) {
                var networkInterface = interfaces.nextElement();
                if (networkInterface.isLoopback() || networkInterface.isVirtual() || !networkInterface.isUp()) {
                    continue;
                }
                var addresses = networkInterface.getInetAddresses();
                while (addresses.hasMoreElements()) {
                    var address = addresses.nextElement();
                    var ip = address.getHostAddress();
                    if (ip.matches("\\d+\\.\\d+\\.\\d+\\.\\d+")) {
                        return ip;
                    }
                }
            }
        } catch (Exception e) {
            System.err.println("获取本地IP地址失败: " + e.getMessage());
        }
        return null;
    }
}